package com.xsq.audio;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.media.MediaBrowserServiceCompat;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.util.Util;
import com.xsq.audio.player.PlayNotificationManager;
import com.xsq.audio.player.utils.LogUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * 音频播放服务
 * Created by Shiquan.Xiao on 2023/3/28.
 */
public class AudioService extends MediaBrowserServiceCompat {

    private static final String AUDIO_SESSION_TAG = "AudioService";
    //当前播放的下标返回结果
    private ExoPlayer exoPlayer;
    private MediaSessionCompat audioSession;
    private MediaSessionConnector sessionConnector;
    private PlayNotificationManager playNotificationManager;
    private ResultReceiver rootResultReceiver;
    private final PlayerEventListener playerListener = new PlayerEventListener();
    private List<MediaItem> mediaItemList = new ArrayList<>();
    private List<MediaMetadataCompat> curPlaylist = new ArrayList<>();
    private final AudioDataSource audioDataSource = new AudioDataSource();
    private int curIndex = 0;
    private boolean isForegroundService = false;

    @Override
    public void onCreate() {
        super.onCreate();

        Intent launchIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, launchIntent, PendingIntent.FLAG_MUTABLE);

        audioSession = new MediaSessionCompat(getApplicationContext(), AUDIO_SESSION_TAG);
        audioSession.setSessionActivity(pendingIntent);
        audioSession.setActive(true);

        sessionConnector = new MediaSessionConnector(audioSession);
        sessionConnector.setPlaybackPreparer(new PlaybackPreparer());
        sessionConnector.setQueueNavigator(new AudioTimelineQueueNavigator(audioSession));

        MediaSessionCompat.Token sessionToken = audioSession.getSessionToken();
        // 设置会话的令牌，以便客户端活动可以与其通信。
        setSessionToken(sessionToken);

        playNotificationManager = new PlayNotificationManager(this, sessionToken, new PlayNotificationListener());

        exoPlayer = new ExoPlayer.Builder(getApplicationContext()).build();
        exoPlayer.setAudioAttributes(new AudioAttributes.Builder()
                .setContentType(C.CONTENT_TYPE_MUSIC)
                .setUsage(C.USAGE_MEDIA)
                .build(), true);
        exoPlayer.setHandleAudioBecomingNoisy(true);
        exoPlayer.addListener(playerListener);

        playNotificationManager.showNotificationForPlayer(exoPlayer);

        changePlayer(null, exoPlayer);
    }

    private void preparePlay(
            List<MediaMetadataCompat> metadataList,
            MediaMetadataCompat playData,
            boolean whenReady,
            long pos
    ) {
        int playIndex = 0;
        if (playData != null) {
            playIndex = metadataList.indexOf(playData);
        }
        curPlaylist = metadataList;
        exoPlayer.setPlayWhenReady(whenReady);
        exoPlayer.stop();

        exoPlayer.setMediaItems(mediaItemList, playIndex, pos);
        exoPlayer.prepare();
    }

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
        Bundle rootExtras = new Bundle();
        return new BrowserRoot(AudioDataSource.BROWSER_ROOT_ID, rootExtras);
    }

    @Override
    public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
        if (parentId.equals(AudioDataSource.BROWSER_ROOT_ID)) {
            List<MediaMetadataCompat> dataResults = audioDataSource.getDataForRootId(parentId);
            List<MediaBrowserCompat.MediaItem> resultList = new ArrayList<>();

            if (dataResults != null) {
                for (MediaMetadataCompat data : dataResults) {
                    resultList.add(new MediaBrowserCompat.MediaItem(data.getDescription(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
                }
            }
            result.sendResult(resultList);
        } else {
            result.sendResult(null);
        }
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        savePlayingAudio();
        super.onTaskRemoved(rootIntent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        release();
    }

    private void release() {
        exoPlayer.removeListener(playerListener);
        exoPlayer.release();
        audioDataSource.release();
    }

    private void savePlayingAudio() {
        if (curPlaylist.isEmpty()) return;

        MediaDescriptionCompat description = curPlaylist.get(curIndex).getDescription();
        long position = exoPlayer.getCurrentPosition();
        AudioDataSource.savePlayingAudio(description, position);
    }

    private void sendAudioPlayChange(MediaMetadataCompat metadata) {
        if (rootResultReceiver != null) {
            Bundle bundle = new Bundle();
            bundle.putParcelable(AudioDataSource.EXTRA_AUDIO_PLAY_DATA, metadata);
            rootResultReceiver.send(AudioDataSource.RESULT_CODE_AUDIO_PLAY_CHANGE, bundle);
        }
    }

    private void changePlayer(ExoPlayer prePlayer, ExoPlayer newPlayer) {
        if (prePlayer == newPlayer) {
            return;
        }

        exoPlayer = newPlayer;
        if (prePlayer != null) {
            if (curPlaylist.isEmpty()) {
                exoPlayer.clearMediaItems();
                exoPlayer.stop();
            } else if (prePlayer.getPlaybackState() != Player.STATE_IDLE && prePlayer.getPlaybackState() != Player.STATE_ENDED) {
                preparePlay(curPlaylist, curPlaylist.get(curIndex), prePlayer.getPlayWhenReady(), prePlayer.getCurrentPosition());
            }
        }

        sessionConnector.setPlayer(exoPlayer);
        exoPlayer.stop(true);
    }

    private class AudioTimelineQueueNavigator extends TimelineQueueNavigator{

        public AudioTimelineQueueNavigator(MediaSessionCompat mediaSession) {
            super(mediaSession);
        }

        @NonNull
        @Override
        public MediaDescriptionCompat getMediaDescription(@NonNull Player player, int windowIndex) {
            if (windowIndex < curPlaylist.size()) {
                return curPlaylist.get(windowIndex).getDescription();
            }
            return new MediaDescriptionCompat.Builder().build();
        }

        /**
         * 下一首
         */
        @Override
        public void onSkipToNext(@NonNull Player player) {
            if (player.hasNextMediaItem()) {
                player.seekToNextMediaItem();
            }
        }

        /**
         * 上一首
         */
        @Override
        public void onSkipToPrevious(@NonNull Player player) {
            if (player.hasPreviousMediaItem()) {
                player.seekToPreviousMediaItem();
            }
        }

        /**
         * 跳到指定的队列id的播放项
         * @param id 队列选项的id
         */
        @Override
        public void onSkipToQueueItem(@NonNull Player player, long id) {
            int lastIndex = curPlaylist.size() - 1;
            List<MediaSessionCompat.QueueItem> queues = audioSession.getController().getQueue();
            int queueSize = queues.size();

            MediaMetadataCompat skipData = null;
            if (lastIndex == curIndex) {
                for (int i = queueSize - 1; i >= 0; i--) {
                    MediaSessionCompat.QueueItem queueItem = queues.get(i);
                    if (queueItem.getQueueId() == id) {
                        skipData = curPlaylist.get(i);
                        break;
                    }
                }
            } else {
                for (int i = 0; i < queueSize; i++) {
                    MediaSessionCompat.QueueItem queueItem = queues.get(i);
                    if (queueItem.getQueueId() == id) {
                        skipData = curPlaylist.get(i);
                        break;
                    }
                }
            }

            if (skipData != null) {
                preparePlay(curPlaylist, skipData, player.getPlayWhenReady(), C.INDEX_UNSET);
            }
        }
    };

    private class PlayNotificationListener implements PlayerNotificationManager.NotificationListener {

        @Override
        public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) {
            if (ongoing && !isForegroundService) {
                ContextCompat.startForegroundService(getApplicationContext(), new Intent(getApplicationContext(), AudioService.class));

                startForeground(notificationId, notification);
                isForegroundService = true;
            }
        }

        @Override
        public void onNotificationCancelled(int notificationId, boolean dismissedByUser) {
            stopForeground(true);
            isForegroundService = false;
            stopSelf();
        }
    }

    private class PlaybackPreparer implements MediaSessionConnector.PlaybackPreparer {

        @Override
        public long getSupportedPrepareActions() {
            return PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID |
                    PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID |
                    PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH |
                    PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH;
        }

        @Override
        public void onPrepare(boolean playWhenReady) {
            LogUtil.m( "Player onPrepare");
        }

        @Override
        public void onPrepareFromMediaId(@NonNull String mediaId, boolean playWhenReady, @Nullable Bundle extras) {
            LogUtil.m("Player onPrepareFromMediaId");

            for (int i = 0; i< curPlaylist.size(); i++) {
                MediaMetadataCompat data = curPlaylist.get(i);
                if (data.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID).equals(mediaId)) {
                    preparePlay(curPlaylist, data, playWhenReady, C.TIME_UNSET);
                }
            }
        }

        @Override
        public void onPrepareFromSearch(@NonNull String query, boolean playWhenReady, @Nullable Bundle extras) {
        }

        @Override
        public void onPrepareFromUri(@NonNull Uri uri, boolean playWhenReady, @Nullable Bundle extras) {
        }

        @Override
        public boolean onCommand(@NonNull Player player, @NonNull String command, @Nullable Bundle extras, @Nullable ResultReceiver cb) {
            LogUtil.m("Player command: "+command);

            if (command.equals(AudioDataSource.BROWSER_ROOT_ID)) {
                if (extras != null) {
                    @SuppressWarnings("unchecked")
                    List<AudioItemData> audioItemList = (List<AudioItemData>) extras.getSerializable(AudioDataSource.EXTRA_ROOT_ID_DATA);
                    audioDataSource.putToRootData(command, audioItemList);
                }

                rootResultReceiver = cb;
                List<MediaMetadataCompat> list = audioDataSource.getDataForRootId(command);
                if (list != null && list.size() > 0) {
                    curIndex = 0;
                    curPlaylist = list;

                    mediaItemList = AudioDataSource.convertDataToMediaItem(list);
                } else {
                    player.clearMediaItems();
                    player.stop();
                }
            } else if (command.equals(AudioDataSource.BROWSER_CONTROLLER_CLOSE)) {
                playNotificationManager.hideNotification();
                audioSession.release();
            }

            return false;
        }
    }

    private class PlayerEventListener implements Player.Listener {

        @Override
        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            if (playbackState == Player.STATE_READY) {
                savePlayingAudio();

                if (!playWhenReady) {
                    stopForeground(false);
                    isForegroundService = false;
                }
            } else if (playbackState == Player.STATE_BUFFERING){
                playNotificationManager.showNotificationForPlayer(exoPlayer);
            } else {
                playNotificationManager.hideNotification();
            }
        }

        @Override
        public void onEvents(@NonNull Player player, @NonNull Player.Events events) {
            LogUtil.i("onEvents---> Player event: "+eventLog(events));
            if (events.contains(Player.EVENT_POSITION_DISCONTINUITY) ||
                    events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION) ||
                    events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED) ||
                    events.contains(Player.EVENT_IS_LOADING_CHANGED)) {
                if (!curPlaylist.isEmpty()) {
                    int currentMediaItemIndex = player.getCurrentMediaItemIndex();
                    if (currentMediaItemIndex != curIndex) {
                        curIndex = Util.constrainValue(player.getCurrentMediaItemIndex(), 0, curPlaylist.size());
                    }
                } else {
                    curIndex = 0;
                }
                sendCurrentIndex();
            }

            //切换歌曲事件
            if (events.contains(Player.EVENT_MEDIA_METADATA_CHANGED) && events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
                if (!curPlaylist.isEmpty()) {
                    sendAudioPlayChange(curPlaylist.get(curIndex));
                }
            }
        }

        @Override
        public void onPlayerError(PlaybackException error) {
            LogUtil.m("Player error: " + error.getErrorCodeName() + " (" + error.errorCode + ")");

            String message = "应用程序遇到意外错误";
            if (error.errorCode == PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS
                    || error.errorCode == PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND) {
                message = "无法定位请求的媒体";
            }
            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
        }

        private String eventLog(Player.Events events) {
            StringBuilder event = new StringBuilder();
            for (int i = 0; i < events.size(); i++) {
                event.append(events.get(i)).append(",");
            }
            return event.toString();
        }

        private void sendCurrentIndex() {
            if (rootResultReceiver != null) {
                Bundle bundle = new Bundle();
                bundle.putInt(AudioDataSource.EXTRA_AUDIO_ITEM_INDEX, curIndex);
                rootResultReceiver.send(AudioDataSource.RESULT_CODE_AUDIO_ITEM_INDEX, bundle);
            }
        }
    }

}
